/*
 * nuseserver.cc
 */

#include "../protocol.h"
#include "server.h"
#include "database.h"
#include "../connection.h"
#include "../connectionclosedexception.h"

#include <iostream>
#include <sstream>
#include <string>
#include <sstream>
#include <cstdlib>
#include <time.h>
#include <csignal>

using namespace std;
using namespace protocol;
using namespace database;
using client_server::Server;
using client_server::Connection;
using client_server::ConnectionClosedException;

bool sigterm = false;
int port;

/*
 * Malformed message
 */
void error(unsigned char cmd) {
	cerr << "Unrecognized command: " << static_cast<int>(cmd) << endl;
	cerr << "Connection terminated." << endl;
	
}

/*
 * Catch terminating signal
 */
void terminate(int)
{
        std::cout << "Shutting down server..." << std::endl;
        sigterm = true;
        Connection c("localhost", port);
}

/*
 * Signal a non-existent newsgroup ID
 */
void invalidNg(Connection* conn) {
	conn->write(Protocol::ANS_NAK);
	conn->write(Protocol::ERR_NG_DOES_NOT_EXIST);
}

/*
 * Signal a non-existent article ID
 */
void invalidArt(Connection* conn) {
	conn->write(Protocol::ANS_NAK);
	conn->write(Protocol::ERR_ART_DOES_NOT_EXIST);
}

/*###############################################*/
/*############# Handle client requests ##########*/
/*###############################################*/

/*
 * List newsgroups
 */
void listNgs(Connection& conn, Database& db) {
	list<Newsgroup> ngs = db.listNewsgroups();
	conn.write(Protocol::ANS_LIST_NG);
	conn.writeInt(ngs.size());
	cout << "Sending ng list with " << ngs.size() << " items" << endl;
	for(list<Newsgroup>::iterator it = ngs.begin(); it != ngs.end(); ++it) {
		conn.writeInt(it->id);
		conn.write(it->name);
		cout << "  " << it->id << ". " << it->name << endl;
	}
	conn.write(Protocol::ANS_END);
}

/*
 * Create newsgroup
 */
void createNg(Connection& conn, Database& db) {
	string title = conn.readString();

	conn.write(Protocol::ANS_CREATE_NG);
	try {
		db.addNewsgroup(title);
		conn.write(Protocol::ANS_ACK);
	}
	catch (unsigned char error) {
		conn.write(Protocol::ANS_NAK);
		conn.write(error);
	}
	conn.write(Protocol::ANS_END);
}

/*
 * Delete newsgroup
 */
void delNg(Connection& conn, Database& db) {
	int ng_id = conn.readInt();

	conn.write(Protocol::ANS_DELETE_NG);
	try {
		db.deleteNewsgroup(ng_id);
		conn.write(Protocol::ANS_ACK);
	}
	catch (unsigned char error) {
		conn.write(Protocol::ANS_NAK);
		conn.write(error);
	}
	conn.write(Protocol::ANS_END);
}

/*
 * List articles
 */
void listArts(Connection& conn, Database& db) {
	int ng_id = conn.readInt();

	conn.write(Protocol::ANS_LIST_ART);
	try {
		list<Article> arts = db.listArticles(ng_id);
		conn.write(Protocol::ANS_ACK);
		conn.writeInt(arts.size());
		cout << "Sending arts list with " << arts.size() << " items" << endl;
		for(list<Article>::iterator it = arts.begin(); it != arts.end(); ++it) {
			conn.writeInt(it->id);
			conn.write(it->title);
			cout << "  " << it->id << ". " << it->title << endl;
		}
	}
	catch (unsigned char error) {
		conn.write(Protocol::ANS_NAK);
		conn.write(error);
	}
	conn.write(Protocol::ANS_END);
}


/*
 * Create article
 */
void createArticle(Connection& conn, Database& db) {
	int ng_id = conn.readInt();
	
	//Get current date
	time_t myTime = time(NULL);
	tm *t = localtime(&myTime);
	stringstream out;
	out << t->tm_year+1900 << "-";
	int n = t->tm_mon+1;
	if(n < 10) out << 0;
	out << n << "-";
	n = t->tm_mday;
	if(n < 10) out << 0;
	out << n;
	string date = out.str();
	
	string title = conn.readString();
	string author = conn.readString();
	string text = conn.readString();

	conn.write(Protocol::ANS_CREATE_ART);
	try {
		db.addArticle(ng_id, date, author, title, text);
		conn.write(Protocol::ANS_ACK);
	}
	catch (unsigned char error) {
		conn.write(Protocol::ANS_NAK);
		conn.write(error);
	}
	conn.write(Protocol::ANS_END);
}

/*
 * Delete article
 */
void delArticle(Connection& conn, Database& db) {
	int ng_id = conn.readInt();
	int art_id = conn.readInt();

	conn.write(Protocol::ANS_DELETE_ART);
	try {
		db.deleteArticle(ng_id, art_id);
		conn.write(Protocol::ANS_ACK);
	}
	catch (unsigned char error) {
		conn.write(Protocol::ANS_NAK);
		conn.write(error);
	}
	conn.write(Protocol::ANS_END);
}

/*
 * Get article
 */
void getArticle(Connection& conn, Database& db) {
	int ng_id = conn.readInt();
	int art_id = conn.readInt();

	conn.write(Protocol::ANS_GET_ART);
	try {
		Article art = db.getArticle(ng_id, art_id);
		conn.write(Protocol::ANS_ACK);
		conn.write(art.title);
		conn.write(art.author);
		conn.write(art.text);
	}
	catch (unsigned char error) {
		conn.write(Protocol::ANS_NAK);
		conn.write(error);
	}
	conn.write(Protocol::ANS_END);
}

/*###############################################*/
/*###############################################*/

/*
 * Main
 */
int main(int argc, char* argv[]){

	//Check arguments
	if (argc > 2) {
		cerr << "Usage: nuseserver [port-number]" << endl;
		exit(1);
	}

	//Initialize database
	cout << "Initializing DB" << endl;
	Database db;
	cout << "DB initialized" << endl;
	
	if( argc == 1)
		port = 1201;
	else
		port = atoi(argv[1]);

	Server server(port);

	if (! server.isReady()) {
		cerr << "Server initialization error" << endl;
		exit(1);
	}

        signal(SIGTERM,terminate);
        signal(SIGABRT,terminate);
        signal(SIGINT,terminate);
	while (!sigterm) {
		Connection* conn = server.waitForActivity();
		if (conn != 0) {
			try {
				unsigned char cmd = conn->read();
				cout << "Recieved request: " << static_cast<int>(cmd) << endl;
				switch(cmd) {
				case Protocol::COM_LIST_NG:		// list newsgroups
					listNgs(*conn, db);		break;
				case Protocol::COM_CREATE_NG:	// create newsgroup
					createNg(*conn, db);	break;
				case Protocol::COM_DELETE_NG: 	// delete newsgroup
					delNg(*conn, db);	break;
				case Protocol::COM_LIST_ART:	// list articles
					listArts(*conn, db);	break;
				case Protocol::COM_CREATE_ART:	// create article
					createArticle(*conn, db);break;
				case Protocol::COM_DELETE_ART:	// delete article
					delArticle(*conn, db);	break;
				case Protocol::COM_GET_ART:		// get article
					getArticle(*conn, db);	break;
				default:						// malformed message
					server.deregisterConnection(conn);
					delete conn;
					error(cmd);
					break;
				}
			}
			catch (ConnectionClosedException&) {
				server.deregisterConnection(conn);
				delete conn;
				cout << "Client closed connection" << endl;
				continue;
			}
			if(conn->read() == Protocol::COM_END)
				cout << "Request ended properly" << endl;
			else {
				cout << "Should have read COM_END here" << endl;
				server.deregisterConnection(conn);
				delete conn;
			}
		}
		else {
			server.registerConnection(new Connection);
			if(!sigterm)
			cout << "New client connects" << endl;
		}
	}
}
